// handler.go
package main

import (
	"archive/zip"
	"encoding/json"
	"fmt"
	"image"
	"image/draw"
	_ "image/gif"
	"image/jpeg"
	"image/png"
	"io"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	_ "golang.org/x/image/bmp"
	_ "golang.org/x/image/tiff"
	_ "golang.org/x/image/webp"
)

type Movie struct {
	ID                   int    `json:"id"`
	Name                 string `json:"name"`
	URL                  string `json:"url"`
	ImagePath            string `json:"imagePath"`
	ProcessedImagePath   string `json:"processedImagePath"`
}

var movies []Movie
var movieIDCounter = 0

func homeHandler(w http.ResponseWriter, r *http.Request) {
	homeTemplate.Execute(w, nil)
}

func saveHandler(w http.ResponseWriter, r *http.Request) {
	name := r.FormValue("name")
	url := r.FormValue("url")
	imageUrl := r.FormValue("imageUrl")

	finalImagePath := ""
	finalProcessedImagePath := ""

	if imageFile, _, err := r.FormFile("imageFile"); err == nil {
		defer imageFile.Close()
		originalFilename := filepath.Join("assets", "uploads", filepath.Base(r.FormValue("imageFile")))
		dst, err := os.Create(originalFilename)
		if err != nil {
			http.Error(w, "Failed to save image", http.StatusInternalServerError)
			return
		}
		defer dst.Close()
		io.Copy(dst, imageFile)
		finalImagePath = "/uploads/" + filepath.Base(originalFilename)

		processedImagePath := filepath.Join("assets", "uploads", "processed_"+filepath.Base(originalFilename))
		err = processImageToSquare(originalFilename, processedImagePath)
		if err != nil {
			http.Error(w, fmt.Sprintf("Failed to process image: %v", err), http.StatusInternalServerError)
			return
		}
		finalProcessedImagePath = "/uploads/" + filepath.Base(processedImagePath)
	} else if imageUrl != "" {
		finalImagePath = imageUrl
		ext := ".jpg"
		if strings.Contains(imageUrl, ".png") {
			ext = ".png"
		} else if strings.Contains(imageUrl, ".gif") {
			ext = ".gif"
		} else if strings.Contains(imageUrl, ".webp") {
			ext = ".webp"
		} else if strings.Contains(imageUrl, ".bmp") {
			ext = ".bmp"
		} else if strings.Contains(imageUrl, ".tiff") || strings.Contains(imageUrl, ".tif") {
			ext = ".tiff"
		}
		tempFilename := filepath.Join("assets", "uploads", "temp_"+fmt.Sprintf("%d", movieIDCounter)+ext)
		err := downloadImage(imageUrl, tempFilename)
		if err != nil {
			http.Error(w, fmt.Sprintf("Failed to download image: %v", err), http.StatusInternalServerError)
			return
		}

		processedImagePath := filepath.Join("assets", "uploads", "processed_"+fmt.Sprintf("%d", movieIDCounter)+ext)
		err = processImageToSquare(tempFilename, processedImagePath)
		if err != nil {
			http.Error(w, fmt.Sprintf("Failed to process downloaded image: %v", err), http.StatusInternalServerError)
			return
		}
		finalProcessedImagePath = "/uploads/" + filepath.Base(processedImagePath)
	} else {
		finalImagePath = ""
		finalProcessedImagePath = ""
	}

	movieIDCounter++
	newMovie := Movie{
		ID:                   movieIDCounter,
		Name:                 name,
		URL:                  url,
		ImagePath:            finalImagePath,
		ProcessedImagePath:   finalProcessedImagePath,
	}
	movies = append(movies, newMovie)

	ip := getLocalIP()
	regenerateFiles(ip)

	w.WriteHeader(http.StatusOK)
	fmt.Fprint(w, "Files saved successfully")
}

func editMovieHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	id := r.FormValue("id")
	name := r.FormValue("name")
	url := r.FormValue("url")
	imageUrl := r.FormValue("imageUrl")

	var movie *Movie
	found := false
	for i := range movies {
		if fmt.Sprintf("%d", movies[i].ID) == id {
			movie = &movies[i]
			found = true
			break
		}
	}

	if !found {
		http.Error(w, "Movie not found", http.StatusNotFound)
		return
	}

	movie.Name = name
	movie.URL = url

	if imageFile, _, err := r.FormFile("imageFile"); err == nil {
		defer imageFile.Close()
		originalFilename := filepath.Join("assets", "uploads", fmt.Sprintf("temp_%s_%s", id, filepath.Base(r.FormValue("imageFile"))))
		dst, err := os.Create(originalFilename)
		if err != nil {
			http.Error(w, "Failed to save image", http.StatusInternalServerError)
			return
		}
		defer dst.Close()
		io.Copy(dst, imageFile)
		movie.ImagePath = "/uploads/" + filepath.Base(originalFilename)

		processedImagePath := filepath.Join("assets", "uploads", fmt.Sprintf("processed_%s_%s", id, filepath.Base(originalFilename)))
		err = processImageToSquare(originalFilename, processedImagePath)
		if err != nil {
			http.Error(w, fmt.Sprintf("Failed to process image: %v", err), http.StatusInternalServerError)
			return
		}
		movie.ProcessedImagePath = "/uploads/" + filepath.Base(processedImagePath)
	} else if imageUrl != "" {
		movie.ImagePath = imageUrl
		ext := ".jpg"
		if strings.Contains(imageUrl, ".png") {
			ext = ".png"
		} else if strings.Contains(imageUrl, ".gif") {
			ext = ".gif"
		} else if strings.Contains(imageUrl, ".webp") {
			ext = ".webp"
		} else if strings.Contains(imageUrl, ".bmp") {
			ext = ".bmp"
		} else if strings.Contains(imageUrl, ".tiff") || strings.Contains(imageUrl, ".tif") {
			ext = ".tiff"
		}
		tempFilename := filepath.Join("assets", "uploads", fmt.Sprintf("temp_%s%s", id, ext))
		err := downloadImage(imageUrl, tempFilename)
		if err != nil {
			http.Error(w, fmt.Sprintf("Failed to download image: %v", err), http.StatusInternalServerError)
			return
		}

		processedImagePath := filepath.Join("assets", "uploads", fmt.Sprintf("processed_%s%s", id, ext))
		err = processImageToSquare(tempFilename, processedImagePath)
		if err != nil {
			http.Error(w, fmt.Sprintf("Failed to process downloaded image: %v", err), http.StatusInternalServerError)
			return
		}
		movie.ProcessedImagePath = "/uploads/" + filepath.Base(processedImagePath)
	}

	ip := getLocalIP()
	regenerateFiles(ip)

	w.WriteHeader(http.StatusOK)
	fmt.Fprint(w, "Movie updated successfully")
}

func exportDataHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/zip")
	w.Header().Set("Content-Disposition", "attachment; filename=movie_data.zip")

	zipWriter := zip.NewWriter(w)
	defer zipWriter.Close()

	moviesFile, err := zipWriter.Create("movies.json")
	if err != nil {
		http.Error(w, "Failed to create movies.json in zip", http.StatusInternalServerError)
		return
	}
	moviesJSON, err := json.Marshal(movies)
	if err != nil {
		http.Error(w, "Failed to marshal movies", http.StatusInternalServerError)
		return
	}
	_, err = moviesFile.Write(moviesJSON)
	if err != nil {
		http.Error(w, "Failed to write movies.json", http.StatusInternalServerError)
		return
	}

	txtContent, err := os.ReadFile("output.txt")
	if err == nil {
		txtFile, err := zipWriter.Create("output.txt")
		if err == nil {
			_, err = txtFile.Write(txtContent)
			if err != nil {
				http.Error(w, "Failed to write output.txt", http.StatusInternalServerError)
				return
			}
		}
	}

	m3uContent, err := os.ReadFile("output.m3u")
	if err == nil {
		m3uFile, err := zipWriter.Create("output.m3u")
		if err == nil {
			_, err = m3uFile.Write(m3uContent)
			if err != nil {
				http.Error(w, "Failed to write output.m3u", http.StatusInternalServerError)
				return
			}
		}
	}

	uploadDir := "assets/uploads"
	filepath.Walk(uploadDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return nil
		}
		if !info.IsDir() {
			relPath, err := filepath.Rel("assets", path)
			if err != nil {
				return nil
			}
			file, err := zipWriter.Create("assets/" + relPath)
			if err != nil {
				return nil
			}
			srcFile, err := os.Open(path)
			if err != nil {
				return nil
			}
			defer srcFile.Close()
			_, err = io.Copy(file, srcFile)
			return nil
		}
		return nil
	})
}

func importDataHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	file, _, err := r.FormFile("file")
	if err != nil {
		http.Error(w, "Failed to get file", http.StatusBadRequest)
		return
	}
	defer file.Close()

	tempFile, err := os.CreateTemp("", "import_*.zip")
	if err != nil {
		http.Error(w, "Failed to create temp file", http.StatusInternalServerError)
		return
	}
	defer os.Remove(tempFile.Name())
	defer tempFile.Close()

	_, err = io.Copy(tempFile, file)
	if err != nil {
		http.Error(w, "Failed to save temp file", http.StatusInternalServerError)
		return
	}

	zipReader, err := zip.OpenReader(tempFile.Name())
	if err != nil {
		http.Error(w, "Failed to open zip file", http.StatusBadRequest)
		return
	}
	defer zipReader.Close()

	tempDir, err := os.MkdirTemp("", "import_*")
	if err != nil {
		http.Error(w, "Failed to create temp directory", http.StatusInternalServerError)
		return
	}
	defer os.RemoveAll(tempDir)

	for _, f := range zipReader.File {
		rc, err := f.Open()
		if err != nil {
			continue
		}
		defer rc.Close()

		destinationPath := filepath.Join(tempDir, f.Name)
		if !strings.HasPrefix(destinationPath, filepath.Clean(tempDir)+string(os.PathSeparator)) {
			rc.Close()
			continue
		}

		dir := filepath.Dir(destinationPath)
		os.MkdirAll(dir, 0755)

		if !f.FileInfo().IsDir() {
			dstFile, err := os.Create(destinationPath)
			if err != nil {
				rc.Close()
				continue
			}
			defer dstFile.Close()

			_, err = io.Copy(dstFile, rc)
			if err != nil {
				rc.Close()
				continue
			}
		}
		rc.Close()
	}

	moviesContent, err := os.ReadFile(filepath.Join(tempDir, "movies.json"))
	if err == nil {
		var importedMovies []Movie
		err = json.Unmarshal(moviesContent, &importedMovies)
		if err != nil {
			http.Error(w, "Failed to parse movies.json", http.StatusInternalServerError)
			return
		}

		copyImportedFiles(tempDir)
		movies = importedMovies
		movieIDCounter = 0
		for _, movie := range movies {
			if movie.ID > movieIDCounter {
				movieIDCounter = movie.ID
			}
		}

		ip := getLocalIP()
		fixImagePaths(ip)
		regenerateFiles(ip)
	} else {
		loadMoviesFromFiles()
	}

	w.WriteHeader(http.StatusOK)
	fmt.Fprint(w, "Data imported successfully")
}

func copyImportedFiles(tempDir string) {
	srcTxt := filepath.Join(tempDir, "output.txt")
	if _, err := os.Stat(srcTxt); err == nil {
		dstTxt := "output.txt"
		copyFile(srcTxt, dstTxt)
	}

	srcM3u := filepath.Join(tempDir, "output.m3u")
	if _, err := os.Stat(srcM3u); err == nil {
		dstM3u := "output.m3u"
		copyFile(srcM3u, dstM3u)
	}

	srcAssets := filepath.Join(tempDir, "assets")
	if _, err := os.Stat(srcAssets); err == nil {
		copyDir(srcAssets, "assets")
	}
}

func copyFile(src, dst string) error {
	srcFile, err := os.Open(src)
	if err != nil {
		return err
	}
	defer srcFile.Close()

	dstFile, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer dstFile.Close()

	_, err = io.Copy(dstFile, srcFile)
	return err
}

func copyDir(src, dst string) error {
	return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		relPath, err := filepath.Rel(src, path)
		if err != nil {
			return err
		}

		dstPath := filepath.Join(dst, relPath)

		if info.IsDir() {
			return os.MkdirAll(dstPath, info.Mode())
		}

		return copyFile(path, dstPath)
	})
}

func loadMoviesFromFiles() {
	if _, err := os.Stat("output.txt"); os.IsNotExist(err) {
		return
	}

	content, err := os.ReadFile("output.txt")
	if err != nil {
		return
	}

	lines := strings.Split(string(content), "\n")
	if len(lines) == 0 {
		return
	}

	movies = []Movie{}
	movieIDCounter = 0

	for i := 1; i < len(lines); i++ {
		line := strings.TrimSpace(lines[i])
		if line == "" {
			continue
		}

		parts := strings.Split(line, ",")
		if len(parts) == 2 {
			name := strings.TrimSpace(parts[0])
			url := strings.TrimSpace(parts[1])

			movieIDCounter++
			movies = append(movies, Movie{
				ID:                   movieIDCounter,
				Name:                 name,
				URL:                  url,
				ImagePath:            "",
				ProcessedImagePath:   "",
			})
		}
	}
}

func fixImagePaths(currentIP string) {
	for i := range movies {
		if strings.Contains(movies[i].ImagePath, "http://") {
		}
		if strings.Contains(movies[i].ProcessedImagePath, "http://") {
		}
	}
}

func downloadImage(url, filepath string) error {
	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")

	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("failed to fetch image: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("bad status when downloading image: %s (status code: %d)", resp.Status, resp.StatusCode)
	}

	out, err := os.Create(filepath)
	if err != nil {
		return fmt.Errorf("failed to create file for download: %w", err)
	}
	defer out.Close()

	_, err = io.Copy(out, resp.Body)
	if err != nil {
		return fmt.Errorf("failed to save downloaded image  %w", err)
	}

	return nil
}

func processImageToSquare(srcPath, dstPath string) error {
	file, err := os.Open(srcPath)
	if err != nil {
		return fmt.Errorf("failed to open source file: %w", err)
	}
	defer file.Close()

	img, format, err := image.Decode(file)
	if err != nil {
		return fmt.Errorf("failed to decode image (format detected: '%s', path: '%s'): %w", format, srcPath, err)
	}

	bounds := img.Bounds()
	width := bounds.Dx()
	height := bounds.Dy()

	size := width
	if height < width {
		size = height
	}

	dstImg := image.NewRGBA(image.Rect(0, 0, size, size))

	srcX := (width - size) / 2
	srcY := (height - size) / 2

	draw.Draw(dstImg, dstImg.Bounds(), img, image.Point{srcX, srcY}, draw.Src)

	dstFile, err := os.Create(dstPath)
	if err != nil {
		return fmt.Errorf("failed to create destination file: %w", err)
	}
	defer dstFile.Close()

	if strings.HasSuffix(strings.ToLower(dstPath), ".png") {
		return png.Encode(dstFile, dstImg)
	} else {
		return jpeg.Encode(dstFile, dstImg, &jpeg.Options{Quality: 90})
	}
}

func getMoviesHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(movies)
}

func deleteMovieHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	id := r.FormValue("id")
	if id == "" {
		http.Error(w, "ID is required", http.StatusBadRequest)
		return
	}

	for i, movie := range movies {
		if fmt.Sprintf("%d", movie.ID) == id {
			movies = append(movies[:i], movies[i+1:]...)
			ip := getLocalIP()
			regenerateFiles(ip)
			w.WriteHeader(http.StatusOK)
			fmt.Fprint(w, "Movie deleted successfully")
			return
		}
	}

	http.Error(w, "Movie not found", http.StatusNotFound)
}

func regenerateFiles(ip string) {
	txtFile, err := os.Create("output.txt")
	if err != nil {
		fmt.Printf("Warning: Failed to create output.txt: %v\n", err)
	} else {
		defer txtFile.Close()
		txtFile.WriteString("#genre#电影\n")
		for _, movie := range movies {
			txtFile.WriteString(fmt.Sprintf("%s,%s\n", movie.Name, movie.URL))
		}
	}

	m3uFile, err := os.Create("output.m3u")
	if err != nil {
		fmt.Printf("Warning: Failed to create output.m3u: %v\n", err)
	} else {
		defer m3uFile.Close()
		m3uFile.WriteString("#EXTM3U\n")
		for _, movie := range movies {
			logo := movie.ProcessedImagePath
			if strings.HasPrefix(logo, "/uploads/") {
				logo = fmt.Sprintf("http://%s:%s%s", ip, getCurrentPort(), logo)
			}

			if logo != "" {
				m3uFile.WriteString(fmt.Sprintf("#EXTINF:-1 tvg-logo=\"%s\" group-title=\"\",%s\n", logo, movie.Name))
			} else {
				m3uFile.WriteString(fmt.Sprintf("#EXTINF:-1 group-title=\"\",%s\n", movie.Name))
			}
			m3uFile.WriteString(movie.URL + "\n")
		}
	}

	jsonFile, err := os.Create("movies.json")
	if err != nil {
		fmt.Printf("Warning: Failed to create movies.json: %v\n", err)
	} else {
		defer jsonFile.Close()
		encoder := json.NewEncoder(jsonFile)
		encoder.SetEscapeHTML(false)
		err = encoder.Encode(movies)
		if err != nil {
			fmt.Printf("Warning: Failed to encode movies to JSON: %v\n", err)
		}
	}
}

func getLocalIP() string {
	interfaces, err := net.Interfaces()
	if err != nil {
		return "127.0.0.1"
	}

	var ip192, ip10, ip172 string

	for _, iface := range interfaces {
		if iface.Flags&net.FlagLoopback != 0 || iface.Flags&net.FlagUp == 0 {
			continue
		}

		addrs, err := iface.Addrs()
		if err != nil {
			continue
		}

		for _, addr := range addrs {
			switch v := addr.(type) {
			case *net.IPNet:
				ip := v.IP
				if ip.To4() != nil && !ip.IsLoopback() {
					ipStr := ip.String()
					if strings.HasPrefix(ipStr, "192.168.") {
						if ip192 == "" {
							ip192 = ipStr
						}
					} else if strings.HasPrefix(ipStr, "10.") && !strings.HasPrefix(ipStr, "100.") {
						if ip10 == "" {
							ip10 = ipStr
						}
					} else if strings.HasPrefix(ipStr, "172.") {
						if ip172 == "" {
							ip172 = ipStr
						}
					}
				}
			}
		}
	}

	if ip192 != "" {
		return ip192
	}
	if ip10 != "" {
		return ip10
	}
	if ip172 != "" {
		return ip172
	}

	conn, err := net.Dial("udp", "8.8.8.8:80")
	if err != nil {
		return "127.0.0.1"
	}
	defer conn.Close()

	localAddr := conn.LocalAddr().(*net.UDPAddr)
	return localAddr.IP.String()
}

func copyTxtHandler(w http.ResponseWriter, r *http.Request) {
	ip := getLocalIP()
	fmt.Fprintf(w, "http://%s:%s/output.txt", ip, getCurrentPort())
}

func copyM3uHandler(w http.ResponseWriter, r *http.Request) {
	ip := getLocalIP()
	fmt.Fprintf(w, "http://%s:%s/output.m3u", ip, getCurrentPort())
}

func loadMoviesFromJSON() bool {
	if _, err := os.Stat("movies.json"); os.IsNotExist(err) {
		return false
	}

	file, err := os.Open("movies.json")
	if err != nil {
		fmt.Printf("Warning: Failed to open movies.json: %v\n", err)
		return false
	}
	defer file.Close()

	var loadedMovies []Movie
	decoder := json.NewDecoder(file)
	err = decoder.Decode(&loadedMovies)
	if err != nil {
		fmt.Printf("Warning: Failed to decode movies.json: %v\n", err)
		return false
	}

	movies = loadedMovies
	movieIDCounter = 0
	for _, movie := range movies {
		if movie.ID > movieIDCounter {
			movieIDCounter = movie.ID
		}
	}

	fmt.Println("Successfully loaded movies from movies.json")
	return true
}

func init() {
	os.MkdirAll("assets/uploads", 0755)

	if loadMoviesFromJSON() {
		return
	}

	loadExistingMovies()
}

func loadExistingMovies() {
	if _, err := os.Stat("output.txt"); os.IsNotExist(err) {
		return
	}

	content, err := os.ReadFile("output.txt")
	if err != nil {
		return
	}

	lines := strings.Split(string(content), "\n")
	if len(lines) == 0 {
		return
	}

	movies = []Movie{}
	movieIDCounter = 0

	for i := 1; i < len(lines); i++ {
		line := strings.TrimSpace(lines[i])
		if line == "" {
			continue
		}

		parts := strings.Split(line, ",")
		if len(parts) == 2 {
			name := strings.TrimSpace(parts[0])
			url := strings.TrimSpace(parts[1])

			exists := false
			for _, movie := range movies {
				if movie.Name == name && movie.URL == url {
					exists = true
					break
				}
			}

			if !exists {
				movieIDCounter++
				movies = append(movies, Movie{
					ID:                   movieIDCounter,
					Name:                 name,
					URL:                  url,
					ImagePath:            "",
					ProcessedImagePath:   "",
				})
			}
		}
	}
}